
package com.bysj.lms.filter;


import com.alibaba.fastjson.JSON;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;

import com.bysj.lms.Shiro.JwtToken;
import com.bysj.lms.common.Const;

import com.bysj.lms.common.RedisConst;
import com.bysj.lms.utils.JwtUtil;
import com.bysj.lms.utils.RedisUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;



import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;


/**
 * 这个类最主要的目的是:当请求需要校验权限，token是否具有权限时，构造出主体subject执行login()
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

	private final Logger logger = LoggerFactory.getLogger(JwtFilter.class);

	/**
	 * 判断是否允许通过
	 * @param request
	 * @param response
	 * @param mappedValue
	 * @return
	 */
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		System.out.println("isAccessAllowed方法");
		try{
			return executeLogin(request,response);
		}catch (Exception e){
			// 认证出现异常，传递错误信息msg
			String msg = e.getMessage();
			// 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
			Throwable throwable = e.getCause();
			logger.info("11111111111111111111111"+msg);
			if (throwable != null && throwable instanceof SignatureVerificationException) {
				logger.info("token不正确");
				// 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
				msg = "token或者密钥不正确(" + throwable.getMessage() + ")";

			} else if (throwable != null && throwable instanceof TokenExpiredException) {
				logger.info("token过期了zzzzzzzzzzzzz");
				// 该异常为JWT的AccessToken已过期(TokenExpiredException)，
				// 判断RefreshToken未过期就进行AccessToken刷新
				if (this.refreshToken(request, response)) {
					return true;
				} else {
					msg = "token已过期(" + throwable.getMessage() + ")";
				}
			} else {
				// 应用异常不为空
				if (throwable != null) {
					// 获取应用异常msg
					msg = throwable.getMessage();
				}
			}
			this.responseError401(request,response,msg);
			return false;
		}

	}

	/**
	 * 是否进行登录请求
	 * @param request
	 * @param response
	 * @return
	 */
	@Override
	protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
		System.out.println("isLoginAttempt方法");
		String token=((HttpServletRequest)request).getHeader(Const.TOKEN_HEADER_NAME);
		System.out.println(token);
		if (token!=null){
			return true;
		}
		return false;
	}

	/**
	 * jwt token
	 * @param request
	 * @param response
	 * @return
	 */
	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
		String jwtToken = ((HttpServletRequest)request).getHeader(Const.TOKEN_HEADER_NAME);
		System.out.println(jwtToken);
		if(jwtToken!=null || StringUtils.isNotBlank(jwtToken))
			return new JwtToken(jwtToken);
		return null;
	}

	/**
	 * 执行登录
	 */
	@Override
	protected boolean executeLogin(ServletRequest request, ServletResponse response)
			throws  AuthenticationException {

		logger.info("进入JwtFilter类中...");

		JwtToken token = (JwtToken) createToken(request, response);
		if(token == null){
			responseError401(request,response,"获取到的token为空");
		}
		try {
			logger.debug("提交UserModularRealmAuthenticator查找对应的realm...");

			getSubject(request, response).login(token);

		} catch (AuthenticationException e) {
			logger.debug("捕获到身份认证异常{}", e.getMessage());
			throw e;
		}catch (Exception e){
			logger.error("xxxxxxxxxxxxxx");
			e.printStackTrace();
		}
		return true;
	}
	/**
	 * isAccessAllowed为false时调用，验证失败
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		System.out.println("onAccessDenied");
		responseError401(request,response,"token verify fail");
		return false;
	}




	/**
	 * shiro验证成功调用
	 * @param token
	 * @param subject
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
//	@Override
//	protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
//		System.out.println("onLoginSuccess：");
//		String jwttoken= (String) token.getPrincipal();
//		if (jwttoken!=null){
//			try{
//				if(JwtUtil.verify(jwttoken))
//					return true;
//			}catch (Exception e){
//				Throwable throwable = e.getCause();
//				System.out.println("token验证："+e.getClass());
//				if (e instanceof TokenExpiredException){
//					System.out.println("TokenExpiredException");
////					if (refreshToken(request, response)) {
////						return true;
////					}else {
////						return false;
////					}
//				}
//			}
//		}
//		return true;
//	}



	/**
	 * 拦截器的前置方法，此处进行跨域处理,跨域已在CorsFilter进行全局处理
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 */
	@Override
	protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

		return super.preHandle(request,response);

	}


	/**
	 * 刷新AccessToken，进行判断RefreshToken是否过期，未过期就返回新的AccessToken且继续正常访问
	 * @paramrequest
	 * @param response
	 * @return
	 */
	private boolean refreshToken(ServletRequest request, ServletResponse response) {
		//拿到当前header中token
		String token = ((HttpServletRequest)request).getHeader(Const.TOKEN_HEADER_NAME);
		logger.info("refreshToken token:"+token);
		//获取当前token的账号信息
		String account = JwtUtil.getAccount(token);
		Long currentTime=JwtUtil.getCurrentTime(token);
		logger.info("=========account:"+account);
		logger.info("=========currentTime:"+currentTime);
		// 判断Redis中RefreshToken是否存在
		String refreshToken = RedisConst.PREFIX_SHIRO_REFRESH_TOKEN+account;
		if (RedisUtil.hasKey(refreshToken)) {
			logger.info("==============="+refreshToken);
			// Redis中RefreshToken还存在，获取RefreshToken的时间戳
			Long currentTimeMillisRedis = (Long) RedisUtil.get(refreshToken);
			// 获取当前AccessToken中的时间戳，与RefreshToken的时间戳对比，如果当前时间戳一致，进行AccessToken刷新
			if (currentTimeMillisRedis.equals(currentTime)) {
				// 获取当前最新时间戳
				Long currentTimeMillis =System.currentTimeMillis();
				RedisUtil.set(account, currentTimeMillis, RedisConst.EXRP_HOUR, TimeUnit.SECONDS);
				// 刷新AccessToken，设置时间戳为当前最新时间戳
				token = JwtUtil.sign(account, currentTimeMillis);
				logger.info("===============refreshToken:"+token);
				// 将新刷新的AccessToken再次进行Shiro的登录
				JwtToken jwtToken = new JwtToken(token);
                getSubject(request, response).login(jwtToken);
				HttpServletResponse httpServletResponse = (HttpServletResponse) response;
				httpServletResponse.setHeader(Const.TOKEN_HEADER_NAME, token);
				httpServletResponse.setHeader("Access-Control-Expose-Headers", Const.TOKEN_HEADER_NAME);
				return true;
			}
		}
		return false;
	}

	private void responseError401(ServletRequest req, ServletResponse response, String msg) {
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
		httpServletResponse.setCharacterEncoding("UTF-8");
		httpServletResponse.setContentType("application/json; charset=utf-8");
		PrintWriter out = null;
		try {
			out = httpServletResponse.getWriter();

			// object 转 json字符串
			String data = JSON.toJSONString( msg);

			out.append(data);

		} catch (IOException e) {

			logger.error("response401 : {}",e.getMessage());
		} finally {
			if (out != null) {
				out.close();
			}
		}
	}
}